// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/harfbuzz_font_skia.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <map>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/render_text.h"

namespace gfx {

namespace {

    class HarfBuzzFace;

    // Maps from code points to glyph indices in a font.
    typedef std::map<uint32_t, uint16_t> GlyphCache;

    typedef std::pair<HarfBuzzFace, GlyphCache> FaceCache;

    // Font data provider for HarfBuzz using Skia. Copied from Blink.
    // TODO(ckocagil): Eliminate the duplication. http://crbug.com/368375
    struct FontData {
        FontData(GlyphCache* glyph_cache)
            : glyph_cache_(glyph_cache)
        {
        }

        SkPaint paint_;
        GlyphCache* glyph_cache_;
    };

    // Deletes the object at the given pointer after casting it to the given type.
    template <typename Type>
    void DeleteByType(void* data)
    {
        Type* typed_data = reinterpret_cast<Type*>(data);
        delete typed_data;
    }

    template <typename Type>
    void DeleteArrayByType(void* data)
    {
        Type* typed_data = reinterpret_cast<Type*>(data);
        delete[] typed_data;
    }

    // Outputs the |width| and |extents| of the glyph with index |codepoint| in
    // |paint|'s font.
    void GetGlyphWidthAndExtents(SkPaint* paint,
        hb_codepoint_t codepoint,
        hb_position_t* width,
        hb_glyph_extents_t* extents)
    {
        DCHECK_LE(codepoint, std::numeric_limits<uint16_t>::max());
        paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);

        SkScalar sk_width;
        SkRect sk_bounds;
        uint16_t glyph = static_cast<uint16_t>(codepoint);

        paint->getTextWidths(&glyph, sizeof(glyph), &sk_width, &sk_bounds);
        if (width)
            *width = SkScalarToFixed(sk_width);
        if (extents) {
            // Invert y-axis because Skia is y-grows-down but we set up HarfBuzz to be
            // y-grows-up.
            extents->x_bearing = SkScalarToFixed(sk_bounds.fLeft);
            extents->y_bearing = SkScalarToFixed(-sk_bounds.fTop);
            extents->width = SkScalarToFixed(sk_bounds.width());
            extents->height = SkScalarToFixed(-sk_bounds.height());
        }
    }

    // Writes the |glyph| index for the given |unicode| code point. Returns whether
    // the glyph exists, i.e. it is not a missing glyph.
    hb_bool_t GetGlyph(hb_font_t* font,
        void* data,
        hb_codepoint_t unicode,
        hb_codepoint_t variation_selector,
        hb_codepoint_t* glyph,
        void* user_data)
    {
        FontData* font_data = reinterpret_cast<FontData*>(data);
        GlyphCache* cache = font_data->glyph_cache_;

        bool exists = cache->count(unicode) != 0;
        if (!exists) {
            SkPaint* paint = &font_data->paint_;
            paint->setTextEncoding(SkPaint::kUTF32_TextEncoding);
            paint->textToGlyphs(&unicode, sizeof(hb_codepoint_t), &(*cache)[unicode]);
        }
        *glyph = (*cache)[unicode];
        return !!*glyph;
    }

    // Returns the horizontal advance value of the |glyph|.
    hb_position_t GetGlyphHorizontalAdvance(hb_font_t* font,
        void* data,
        hb_codepoint_t glyph,
        void* user_data)
    {
        FontData* font_data = reinterpret_cast<FontData*>(data);
        hb_position_t advance = 0;

        GetGlyphWidthAndExtents(&font_data->paint_, glyph, &advance, 0);
        return advance;
    }

    hb_bool_t GetGlyphHorizontalOrigin(hb_font_t* font,
        void* data,
        hb_codepoint_t glyph,
        hb_position_t* x,
        hb_position_t* y,
        void* user_data)
    {
        // Just return true, like the HarfBuzz-FreeType implementation.
        return true;
    }

    hb_position_t GetGlyphKerning(FontData* font_data,
        hb_codepoint_t first_glyph,
        hb_codepoint_t second_glyph)
    {
        SkTypeface* typeface = font_data->paint_.getTypeface();
        const uint16_t glyphs[2] = { static_cast<uint16_t>(first_glyph),
            static_cast<uint16_t>(second_glyph) };
        int32_t kerning_adjustments[1] = { 0 };

        if (!typeface->getKerningPairAdjustments(glyphs, 2, kerning_adjustments))
            return 0;

        SkScalar upm = SkIntToScalar(typeface->getUnitsPerEm());
        SkScalar size = font_data->paint_.getTextSize();
        return SkScalarToFixed(
            SkScalarMulDiv(SkIntToScalar(kerning_adjustments[0]), size, upm));
    }

    hb_position_t GetGlyphHorizontalKerning(hb_font_t* font,
        void* data,
        hb_codepoint_t left_glyph,
        hb_codepoint_t right_glyph,
        void* user_data)
    {
        FontData* font_data = reinterpret_cast<FontData*>(data);
        if (font_data->paint_.isVerticalText()) {
            // We don't support cross-stream kerning.
            return 0;
        }

        return GetGlyphKerning(font_data, left_glyph, right_glyph);
    }

    hb_position_t GetGlyphVerticalKerning(hb_font_t* font,
        void* data,
        hb_codepoint_t top_glyph,
        hb_codepoint_t bottom_glyph,
        void* user_data)
    {
        FontData* font_data = reinterpret_cast<FontData*>(data);
        if (!font_data->paint_.isVerticalText()) {
            // We don't support cross-stream kerning.
            return 0;
        }

        return GetGlyphKerning(font_data, top_glyph, bottom_glyph);
    }

    // Writes the |extents| of |glyph|.
    hb_bool_t GetGlyphExtents(hb_font_t* font,
        void* data,
        hb_codepoint_t glyph,
        hb_glyph_extents_t* extents,
        void* user_data)
    {
        FontData* font_data = reinterpret_cast<FontData*>(data);

        GetGlyphWidthAndExtents(&font_data->paint_, glyph, 0, extents);
        return true;
    }

    class FontFuncs {
    public:
        FontFuncs()
            : font_funcs_(hb_font_funcs_create())
        {
            hb_font_funcs_set_glyph_func(font_funcs_, GetGlyph, 0, 0);
            hb_font_funcs_set_glyph_h_advance_func(
                font_funcs_, GetGlyphHorizontalAdvance, 0, 0);
            hb_font_funcs_set_glyph_h_kerning_func(
                font_funcs_, GetGlyphHorizontalKerning, 0, 0);
            hb_font_funcs_set_glyph_h_origin_func(
                font_funcs_, GetGlyphHorizontalOrigin, 0, 0);
            hb_font_funcs_set_glyph_v_kerning_func(
                font_funcs_, GetGlyphVerticalKerning, 0, 0);
            hb_font_funcs_set_glyph_extents_func(
                font_funcs_, GetGlyphExtents, 0, 0);
            hb_font_funcs_make_immutable(font_funcs_);
        }

        ~FontFuncs()
        {
            hb_font_funcs_destroy(font_funcs_);
        }

        hb_font_funcs_t* get() { return font_funcs_; }

    private:
        hb_font_funcs_t* font_funcs_;

        DISALLOW_COPY_AND_ASSIGN(FontFuncs);
    };

    base::LazyInstance<FontFuncs>::Leaky g_font_funcs = LAZY_INSTANCE_INITIALIZER;

    // Returns the raw data of the font table |tag|.
    hb_blob_t* GetFontTable(hb_face_t* face, hb_tag_t tag, void* user_data)
    {
        SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data);

        const size_t table_size = typeface->getTableSize(tag);
        if (!table_size)
            return 0;

        scoped_ptr<char[]> buffer(new char[table_size]);
        if (!buffer)
            return 0;
        size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer.get());
        if (table_size != actual_size)
            return 0;

        char* buffer_raw = buffer.release();
        return hb_blob_create(buffer_raw, table_size, HB_MEMORY_MODE_WRITABLE,
            buffer_raw, DeleteArrayByType<char>);
    }

    void UnrefSkTypeface(void* data)
    {
        SkTypeface* skia_face = reinterpret_cast<SkTypeface*>(data);
        SkSafeUnref(skia_face);
    }

    // Wrapper class for a HarfBuzz face created from a given Skia face.
    class HarfBuzzFace {
    public:
        HarfBuzzFace()
            : face_(NULL)
        {
        }

        ~HarfBuzzFace()
        {
            if (face_)
                hb_face_destroy(face_);
        }

        void Init(SkTypeface* skia_face)
        {
            SkSafeRef(skia_face);
            face_ = hb_face_create_for_tables(GetFontTable, skia_face, UnrefSkTypeface);
            DCHECK(face_);
        }

        hb_face_t* get()
        {
            return face_;
        }

    private:
        hb_face_t* face_;
    };

} // namespace

// Creates a HarfBuzz font from the given Skia face and text size.
hb_font_t* CreateHarfBuzzFont(SkTypeface* skia_face,
    SkScalar text_size,
    const FontRenderParams& params,
    bool subpixel_rendering_suppressed)
{
    // TODO(ckocagil): This shouldn't grow indefinitely. Maybe use base::MRUCache?
    static std::map<SkFontID, FaceCache> face_caches;

    FaceCache* face_cache = &face_caches[skia_face->uniqueID()];
    if (face_cache->first.get() == NULL)
        face_cache->first.Init(skia_face);

    hb_font_t* harfbuzz_font = hb_font_create(face_cache->first.get());
    const int scale = SkScalarToFixed(text_size);
    hb_font_set_scale(harfbuzz_font, scale, scale);
    FontData* hb_font_data = new FontData(&face_cache->second);
    hb_font_data->paint_.setTypeface(skia_face);
    hb_font_data->paint_.setTextSize(text_size);
    // TODO(ckocagil): Do we need to update these params later?
    internal::ApplyRenderParams(params, subpixel_rendering_suppressed,
        &hb_font_data->paint_);
    hb_font_set_funcs(harfbuzz_font, g_font_funcs.Get().get(), hb_font_data,
        DeleteByType<FontData>);
    hb_font_make_immutable(harfbuzz_font);
    return harfbuzz_font;
}

} // namespace gfx
